1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.image;
12 public import hip.api.data.image;
13 
14 
15 IHipBMPDecoder bmp;
16 IHipJPEGDecoder jpeg;
17 IHipPNGDecoder png;
18 IHipWebPDecoder webP;
19 
20 
21 version(HipARSDImageDecoder)
22 final class HipARSDImageDecoder : IHipAnyImageDecoder
23 {
24     import arsd.image;
25     MemoryImage img;
26     TrueColorImage trueImg;
27     string path;
28     this(string path = "")
29     {
30         this.path = path;
31     }
32     bool startDecoding(ubyte[] data, void delegate() onSuccess, void delegate() onFailure)
33     {
34         img = loadImageFromMemory(data);
35         if(img !is null)
36         {
37             trueImg = img.getAsTrueColorImage;
38             onSuccess();
39         }
40         else
41             onFailure();
42 
43         return (img !is null) && (trueImg !is null);
44     }
45 
46     uint getWidth() const
47     {
48         if(img !is null)
49             return img.width;
50         return 0;
51     }
52 
53     uint getHeight() const
54     {
55         if(img !is null)
56             return img.height;
57         return 0;
58     }
59 
60     const(ubyte[]) getPixels()  const
61     {
62         if(img !is null)
63             return trueImg.imageData.bytes;
64         return null;
65     }
66 
67     ubyte getBytesPerPixel() const
68     {
69         //Every true image color has 4 bytes per pixel
70         return 4;
71     }
72 
73     ubyte[] getPalette() const
74     {
75         return null;
76     }
77 
78     void dispose()
79     {
80         img.clearInternal;
81         destroy(trueImg);
82         destroy(img);
83     }
84 }
85 
86 
87 version(HipGamutImageDecoder)
88 final class HipGamutImageDecoder : IHipAnyImageDecoder
89 {
90     import gamut;
91     Image img;
92     string path;
93     this(string path = "")
94     {
95         this.path = path;
96     }
97     bool startDecoding(ubyte[] data, void delegate() onSuccess, void delegate() onFailure)
98     {
99         img.loadFromMemory(data, LOAD_RGB | LOAD_ALPHA | LOAD_8BIT);
100         if(img.isValid)
101         {
102             img.changeLayout(LAYOUT_GAPLESS | LAYOUT_VERT_STRAIGHT);
103             onSuccess();
104         }
105         else
106             onFailure();
107         return img.isValid;
108     }
109 
110     uint getWidth() const
111     {
112         if(img.isValid)
113             return img.width;
114         return 0;
115     }
116 
117     uint getHeight() const
118     {
119         if(img.isValid)
120             return img.height;
121         return 0;
122     }
123 
124     const(ubyte[]) getPixels()  const
125     {
126         if(img.isValid)
127             return img.allPixelsAtOnce();
128         return null;
129     }
130 
131     ubyte getBytesPerPixel() const
132     {
133         final switch(img.type) with(PixelType)
134         {
135             case l8: return 1;
136             case l16: return 2;
137             case lf32: return 4;
138             case la8: return 2;
139             case la16: return 4;
140             case laf32: return 8;
141             case rgb8: return 3;
142             case rgb16: return 6;
143             case rgbf32: return 12;
144             case rgba8: return 4;
145             case rgba16: return 8;
146             case rgbaf32: return 16;
147             case unknown: assert(false, "Invalid image?");
148         }
149     }
150     ///Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.
151     ubyte[] getPalette() const{return null;}
152 
153     void dispose()
154     {
155         destroy(img);
156     }
157 }
158 
159 final class HipNullImageDecoder : IHipAnyImageDecoder
160 {
161     this(string path){}
162     bool startDecoding(void[] data, void delegate() onSuccess, void delegate() onFailure)
163     {
164         onFailure();
165         return false;
166     }
167     uint getWidth() const {return 0;}
168     uint getHeight() const {return 0;}
169     const(ubyte)[] getPixels() const {return null;}
170     ubyte getBytesPerPixel() const {return 0;}
171     const(ubyte)[] getPalette() const {return null;}
172     void dispose(){}
173 }
174 
175 
176 version(WebAssembly)
177 {
178     import hip.wasm;
179     extern(C) struct BrowserImage
180     {
181         size_t handle;
182         bool valid() const {return handle > 0;}
183         alias handle this;
184     }
185     //Returns a BrowserImage, but can't use it in type directly.
186     extern(C) size_t WasmDecodeImage(
187         size_t imgPathLength, char* imgPathChars, ubyte* data, size_t dataSize,
188         JSDelegateType!(void delegate(BrowserImage)) onImageLoad
189     );
190 
191     extern(C) size_t WasmImageGetWidth(size_t);
192     extern(C) size_t WasmImageGetHeight(size_t);
193     extern(C) ubyte* WasmImageGetPixels(size_t);
194     extern(C) void WasmImageDispose(size_t);
195 
196     final class HipWasmImageDecoder : IHipAnyImageDecoder
197     {
198         //Everything here needs to be cached for not calling the Wasm bridge.
199         private uint width, height;
200         private size_t timePixelsGet = 0;
201         BrowserImage img;
202         string path;
203         ubyte[] pixels;
204         this(string path)
205         {
206             assert(path, "HipWasmImageDecoder requires a path.");
207             this.path = path;
208         }
209         bool startDecoding(void[] data, void delegate() onSuccess, void delegate() onFailure)
210         {
211             import hip.console.log;
212             img = WasmDecodeImage(path.length, cast(char*)path.ptr, cast(ubyte*)data.ptr, data.length, sendJSDelegate!((BrowserImage _img)
213             {
214                 assert(img == _img, "Different image returned!");
215                 if(img.valid)
216                 {
217                     width = WasmImageGetWidth(img);
218                     height = WasmImageGetHeight(img);
219                     pixels = getWasmBinary(WasmImageGetPixels(img));
220                     hiplog(width, " x ", height, " ", pixels.length, " bytes");
221 
222                     (width != 0 && height != 0) ? onSuccess() : onFailure();
223                 }
224                 else
225                 {
226                     loglnError("Corrupted JS image object.");
227                     onFailure();
228                 }
229             }).tupleof);
230 
231             return img.valid && width != 0 && height != 0;
232         }
233         uint getWidth() const {return width;}
234         uint getHeight() const {return height;}
235         const(ubyte)[] getPixels() const 
236         {
237             return cast(const(ubyte)[])pixels;
238         }
239         ubyte getBytesPerPixel() const {return 4;}
240         const(ubyte)[] getPalette() const {return null;}
241         void dispose()
242         {
243             assert(img.valid, "Invalid dispose call.");
244             WasmImageDispose(img);
245             freeWasmBinary(pixels);
246             img = 0;
247             pixels = null;
248         }
249     }
250 }
251 
252 
253 public class HipImageImpl : IImage
254 {
255     IHipImageDecoder decoder;
256     string imagePath;
257     int width, height;
258     ubyte bytesPerPixel;
259     ushort bitsPerPixel;
260     
261     ubyte[] pixels;
262     this(string path = "")
263     {
264         imagePath = path;
265         decoder = new HipPlatformImageDecoder(path);
266     }
267 
268     static immutable(IImage) getPixelImage()
269     {
270         __gshared HipImageImpl img; 
271         __gshared ubyte[4] pixel = IHipImageDecoder.getPixel();
272         if(img is null)
273         {
274             img = new HipImageImpl("Pixel");
275             img.pixels = pixel;
276             img.width = 1;
277             img.height = 1;
278             img.bytesPerPixel = 4;
279         }
280         return cast(immutable)img;
281     }
282     string getName() const {return imagePath;}
283     uint getWidth() const {return width;}
284     uint getHeight() const {return height;}
285     ubyte getBytesPerPixel() const {return bytesPerPixel;}
286     const(ubyte[]) getPalette() const {return decoder.getPalette;}
287     const(ubyte[]) getPixels() const {return pixels;}
288 
289     void loadRaw(in ubyte[] pixels, int width, int height, ubyte bytesPerPixel)
290     {
291         this.width = width;
292         this.height = height;
293         this.pixels = cast(ubyte[])pixels;
294         this.bytesPerPixel = bytesPerPixel;
295         this.bitsPerPixel = cast(ubyte)(bytesPerPixel*8);
296     }
297 
298 
299     bool loadFromMemory(ubyte[] data, void delegate(IImage self) onSuccess, void delegate() onFailure)
300     {
301         import hip.error.handler;
302         if(ErrorHandler.assertErrorMessage(data.length != 0, "No data was passed to load Image.", "Could not load image"))
303             return false;
304         if(ErrorHandler.assertLazyErrorMessage(decoder.startDecoding(data, ()
305         {
306             width         = decoder.getWidth();
307             height        = decoder.getHeight();
308             bitsPerPixel  = decoder.getBitsPerPixel();
309             bytesPerPixel = decoder.getBytesPerPixel();
310             pixels        = cast(ubyte[])decoder.getPixels();
311             onSuccess(this);
312         }, onFailure),
313         "Decoding Image: ", "Could not load image " ~ imagePath))
314             return false;
315         
316         return true;
317     }
318 
319     bool hasLoadedData() const {return pixels !is null && width != 0 && height != 0;}
320 
321     ubyte[] monochromeToRGBA() const
322     {
323         import hip.error.handler;
324         ubyte[] pix = new ubyte[](4*width*height); //RGBA for each pixel
325         ErrorHandler.assertExit(pix != null, "Out of memory when converting monochrome to RGBA");
326         uint pixelsLength = width*height;
327         ubyte color;
328         uint z;
329         for(uint i = 0; i < pixelsLength; i++)
330         {
331             //Palette r color = palette[pixels[i]*4]
332             color = (cast(ubyte*)pixels)[i];
333             pix[z++] = color; //R
334             pix[z++] = color; //G
335             pix[z++] = color; //B
336             pix[z++] = color; //A
337         }
338 
339         return pix;
340     }
341 
342     ubyte[] convertPalettizedToRGBA() const
343     {
344         import hip.error.handler;
345         ubyte[] pix = new ubyte[](4*width*height); //RGBA for each pixel
346         ErrorHandler.assertExit(pix != null, "Out of memory when converting palette pixels to RGBA");
347 
348         uint pixelsLength = width*height;
349         const(ubyte[]) palette = decoder.getPalette();
350 
351         uint colorIndex;
352         uint z;
353         for(uint i = 0; i < pixelsLength; i++)
354         {
355             //Palette r color = palette[pixels[i]*4]
356             colorIndex = (cast(ubyte*)pixels)[i]*4;
357             pix[z++]   = palette[colorIndex]; //R
358             pix[z++] = palette[colorIndex+1]; //G
359             pix[z++] = palette[colorIndex+2]; //B
360             pix[z++] = palette[colorIndex+3]; //A
361         }
362 
363         return pix;
364     }
365 
366     void dispose()
367     {
368         decoder.dispose();
369     }
370     alias w = width;
371     alias h = height;
372 }
373 
374 
375 ///Use that alias for supporting more platforms
376 version(HipARSDImageDecoder) alias HipPlatformImageDecoder = HipARSDImageDecoder;
377 else version(HipGamutImageDecoder) alias HipPlatformImageDecoder = HipGamutImageDecoder;
378 else version(WebAssembly) alias HipPlatformImageDecoder = HipWasmImageDecoder;
379 else
380 {
381     alias HipPlatformImageDecoder = HipNullImageDecoder;
382     pragma(msg, "WARNING: Using NullImageDecoder.");
383 }